/*
 * Yguan - Yui Gwt Unified ANimation
 *
 * Copyright (c) 2009, Yves Cuillerdier
 * All rights reserved.
 * 
 * This software is licensed under the new BSD License:
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, 
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the documentation 
 * and/or other materials provided with the distribution.
 * 
 * Neither the name of the author nor the names of its contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * @version 0.1.0
 */
package gyraf.core.client.animation;

import gyraf.wrapper.client.JsConfiguration;
import gyraf.wrapper.client.misc.HtmlColor;
import gyraf.wrapper.client.misc.Point;

import com.google.gwt.core.client.JavaScriptObject;

/**
 * The configuration for the Animation.
 * <p>
 * Depending on the type of animation, the YUI animation configuration is a
 * JavaScriptObject like
 * <ul>
 * <li><code>{ top: { from: '10', to: '100', unit: 'em' } }</code> for attributes</li>
 * <li><code>{ backgroundColor: { from: '#FF0000', to: '#FFFFFF' } }</code> for colors<br>
 * Seems not to work in IE !?</li>
 * <li>
 * <code>{ points: { from: [100,0], to: [600, 10], unit: 'px', control: [100, 100] } }</code> for motion</li>
 * <li><code>{ scroll: { from: [100,0], to: [600, 10], unit: 'px', control: [100, 100] } }</code> for scrolling</li>
 * </ul>
 * <p>
 * All these attributes are unified into a single class.
 */
public class AnimationData {

	/** Enumerations for animated attribute properties */
	public enum AnimatedAttribute {
		/** width property */
		width,
		/** height property */
		height,
		/** padding property */
		padding,
		/** margin property */
		margin,
		/** border width property */
		borderWidth,
		/** line height property */
		lineHeight,
		/** opacity property */
		opacity,
		/** font size property */
		fontSize,
	}

	/**
	 * Enumerations for animated color properties.
	 * <p>
	 * This don't seem to work well in IE !?
	 */
	public enum AnimatedColor {
		/** Background color */
		backgroundColor,
		/** foreground color */
		color,
		/** Top border colors */
		borderTopColor,
		/** Right border colors */
		borderRightColor,
		/** Bottom border colors */
		borderBottomColor,
		/** Left border colors */
		borderLeftColor,
	}

	/**
	 * The two following parameters are not parts of the Javascript
	 * Configuration object. However,they are set here following the same
	 * paradigm.
	 */
	private double duration = 1.0d;
	private AnimationEffect easing = AnimationEffect.easeNone;

	/** The configuration objects */
	private final JsConfiguration attributeConfig = new JsConfiguration();
	private final JsConfiguration colorConfig = new JsConfiguration();
	private final JsConfiguration motionConfig = new JsConfiguration();
	private final JsConfiguration scrollConfig = new JsConfiguration();

	/** Tells if the configuration has been set for at least one parameter */
	private boolean hasAttributeConfig = false;
	private boolean hasColorConfig = false;
	private boolean hasMotionConfig = false;
	private boolean hasScrollConfig = false;

	/**
	 * Create a new Animation configuration.
	 */
	public AnimationData() {
		super();
	}

	/**
	 * Set the animation duration (default 1.0sec).
	 * 
	 * @param val
	 *            the duration in seconds.
	 * @return the configuration object for easy chaining.
	 */
	public AnimationData setDuration(final double val) {
		duration = val;
		return this;
	}

	/**
	 * Set the animation Easing (default easeNone).
	 * 
	 * @param val
	 *            the easing method
	 * @return the configuration object for easy chaining.
	 */
	public AnimationData setAnimationEffect(final AnimationEffect val) {
		easing = val;
		return this;
	}

	// ==============================================================
	// Attribute animation
	// ==============================================================
	/**
	 * Animate an element's attribute from current value to given value.
	 * 
	 * @param prop
	 *            the property to animate
	 * @param to
	 *            the final value
	 * @return the configuration object for easy chaining
	 */
	public AnimationData animAttribute(final AnimatedAttribute prop,
			final int to) {
		attributeConfig.setProp(prop.name(), toObject(to));
		hasAttributeConfig = true;
		return this;
	}

	/**
	 * Animate an element's attribute from current value to given value.
	 * 
	 * @param propName
	 *            the property to animate
	 * @param to
	 *            the final value
	 * @return the configuration object for easy chaining
	 */
	public AnimationData animAttribute(final String propName, final int to) {
		attributeConfig.setProp(propName, toObject(to));
		hasAttributeConfig = true;
		return this;
	}

	/**
	 * Animate an element's attribute from current value to given value
	 * specified in given units.
	 * 
	 * @param prop
	 *            the property to animate
	 * @param to
	 *            the final value
	 * @param unit
	 *            any valid css unit ; "px", "em", "%" (default "px")
	 * @return the configuration object for easy chaining
	 */
	public AnimationData animAttribute(final AnimatedAttribute prop,
			final int to, final String unit) {
		attributeConfig.setProp(prop.name(), toUnitObject(to, unit));
		hasAttributeConfig = true;
		return this;
	}

	/**
	 * Animate an element's attribute from current value to given value
	 * specified in given units.
	 * 
	 * @param propName
	 *            the property to animate
	 * @param to
	 *            the final value
	 * @param unit
	 *            any valid css unit ; "px", "em", "%" (default "px")
	 * @return the configuration object for easy chaining
	 */
	public AnimationData animAttribute(final String propName, final int to,
			final String unit) {
		attributeConfig.setProp(propName, toUnitObject(to, unit));
		hasAttributeConfig = true;
		return this;
	}

	/**
	 * Animate an element's attribute from a given value to another. value
	 * 
	 * @param prop
	 *            the property to animate
	 * @param from
	 *            the starting value
	 * @param to
	 *            the final value
	 * @return the configuration object for easy chaining
	 */
	public AnimationData animAttribute(final AnimatedAttribute prop,
			final int from, final int to) {
		attributeConfig.setProp(prop.name(), fromToObject(from, to));
		hasAttributeConfig = true;
		return this;
	}

	/**
	 * Animate an element's attribute from a given value to another. value
	 * 
	 * @param propName
	 *            the property to animate
	 * @param from
	 *            the starting value
	 * @param to
	 *            the final value
	 * @return the configuration object for easy chaining
	 */
	public AnimationData animAttribute(final String propName, final int from,
			final int to) {
		attributeConfig.setProp(propName, fromToObject(from, to));
		hasAttributeConfig = true;
		return this;
	}

	/**
	 * Animate an element's attribute from a given value to another. value
	 * specified in given units.
	 * 
	 * @param prop
	 *            the property to animate
	 * @param from
	 *            the starting value
	 * @param to
	 *            the final value
	 * @param unit
	 *            any valid css unit ; "px", "em", "%" (default "px")
	 * @return the configuration object for easy chaining
	 */
	public AnimationData animAttribute(final AnimatedAttribute prop,
			final int from, final int to, final String unit) {
		attributeConfig.setProp(prop.name(), fromToUnitObject(from, to, unit));
		hasAttributeConfig = true;
		return this;
	}

	/**
	 * Animate an element's attribute from a given value to another. value
	 * specified in given units.
	 * 
	 * @param propName
	 *            the property to animate
	 * @param from
	 *            the starting value
	 * @param to
	 *            the final value
	 * @param unit
	 *            any valid css unit ; "px", "em", "%" (default "px")
	 * @return the configuration object for easy chaining
	 */
	public AnimationData animAttribute(final String propName, final int from,
			final int to, final String unit) {
		attributeConfig.setProp(propName, fromToUnitObject(from, to, unit));
		hasAttributeConfig = true;
		return this;
	}

	// ==============================================================
	// Opacity animation
	// ==============================================================
	/**
	 * Animate an element's opacity from current value to given value.
	 * 
	 * @param to
	 *            the final value (number between 0.0 and 1.0)
	 * @return the configuration object for easy chaining
	 */
	public AnimationData animOpacity(final double to) {
		attributeConfig.setProp("opacity", toObject(to));
		hasAttributeConfig = true;
		return this;
	}

	/**
	 * Animate an element's opacity from a given value to another. value
	 * 
	 * @param from
	 *            the starting value (number between 0.0 and 1.0)
	 * @param to
	 *            the final value (number between 0.0 and 1.0)
	 * @return the configuration object for easy chaining
	 */
	public AnimationData animOpacity(final double from, final double to) {
		attributeConfig.setProp("opacity", fromToObject(from, to));
		hasAttributeConfig = true;
		return this;
	}

	// ==============================================================
	// Color animation
	// ==============================================================
	/**
	 * Animate an element's color from current value to given color.
	 * 
	 * @param prop
	 *            the color property to animate
	 * @param to
	 *            the final color
	 * @return the configuration object for easy chaining
	 */
	public AnimationData animColor(final AnimatedColor prop, final HtmlColor to) {
		colorConfig.setProp(prop.name(), toObject(to.toJsArray()));
		hasColorConfig = true;
		return this;
	}

	/**
	 * Animate an element's color from current value to given color.
	 * 
	 * @param propName
	 *            the color property to animate
	 * @param to
	 *            the final color
	 * @return the configuration object for easy chaining
	 */
	public AnimationData animColor(final String propName, final HtmlColor to) {
		colorConfig.setProp(propName, toObject(to.toJsArray()));
		hasColorConfig = true;
		return this;
	}

	/**
	 * Animate an element's color from a given color to another color.
	 * 
	 * @param prop
	 *            the color property to animate
	 * @param from
	 *            the starting color
	 * @param to
	 *            the final color
	 * @return the configuration object for easy chaining
	 */
	public AnimationData animColor(final AnimatedColor prop,
			final HtmlColor from, final HtmlColor to) {
		colorConfig.setProp(prop.name(), fromToObject(from.toJsArray(), to
				.toJsArray()));
		hasColorConfig = true;
		return this;
	}

	/**
	 * Animate an element's color from a given color to another color.
	 * 
	 * @param propName
	 *            the color property to animate
	 * @param from
	 *            the starting color
	 * @param to
	 *            the final color
	 * @return the configuration object for easy chaining
	 */
	public AnimationData animColor(final String propName, final HtmlColor from,
			final HtmlColor to) {
		colorConfig.setProp(propName, fromToObject(from.toJsArray(), to
				.toJsArray()));
		hasColorConfig = true;
		return this;
	}

	// ==============================================================
	// Motion animation
	// ==============================================================
	/**
	 * Move an element from current value to given value.
	 * 
	 * @param to
	 *            the final value
	 * @return the configuration object for easy chaining
	 */
	public AnimationData move(final Point to) {
		motionConfig.setProp("points", toObject(to.getJsArray()));
		hasMotionConfig = true;
		return this;
	}

	/**
	 * Move an element from current value to given value specified in given
	 * units.
	 * 
	 * @param to
	 *            the final value
	 * @param unit
	 *            any valid css unit ; "px", "em", "%" (default "px")
	 * @return the configuration object for easy chaining
	 */
	public AnimationData move(final Point to, final String unit) {
		motionConfig.setProp("points", toUnitObject(to.getJsArray(), unit));
		hasMotionConfig = true;
		return this;
	}

	/**
	 * Move an element from a given value to another value
	 * 
	 * @param from
	 *            the starting value
	 * @param to
	 *            the final value
	 * @return the configuration object for easy chaining
	 */
	public AnimationData move(final Point from, final Point to) {
		motionConfig.setProp("points", fromToObject(from.getJsArray(), to
				.getJsArray()));
		hasMotionConfig = true;
		return this;
	}

	/**
	 * Move an element from a given value to another value specified in given
	 * units.
	 * 
	 * @param from
	 *            the starting value
	 * @param to
	 *            the final value
	 * @param unit
	 *            any valid css unit ; "px", "em", "%" (default "px")
	 * @return the configuration object for easy chaining
	 */
	public AnimationData move(final Point from, final Point to,
			final String unit) {
		motionConfig.setProp("points", fromToUnitObject(from.getJsArray(), to
				.getJsArray(), unit));
		hasMotionConfig = true;
		return this;
	}

	/**
	 * Set a path for the motion. One of the move methods must have been called
	 * before. Else do nothing!
	 * 
	 * @param path
	 *            a point to pass near
	 * @return the configuration object for easy chaining
	 */
	public AnimationData setMovePath(final Point path) {
		if (hasMotionConfig) {
			setMovePath(motionConfig.getJsElement(), path.getJsArray());
		}
		return this;
	}

	// ==============================================================
	// Scroll animation
	// ==============================================================
	/**
	 * Scroll an element from current value to given value.
	 * 
	 * @param to
	 *            the final value
	 * @return the configuration object for easy chaining
	 */
	public AnimationData scroll(final Point to) {
		scrollConfig.setProp("scroll", toObject(to.getJsArray()));
		hasScrollConfig = true;
		return this;
	}

	/**
	 * Scroll an element from current value to given value specified in given
	 * units.
	 * 
	 * @param to
	 *            the final value
	 * @param unit
	 *            any valid css unit ; "px", "em", "%" (default "px")
	 * @return the configuration object for easy chaining
	 */
	public AnimationData scroll(final Point to, final String unit) {
		scrollConfig.setProp("scroll", toUnitObject(to.getJsArray(), unit));
		hasScrollConfig = true;
		return this;
	}

	/**
	 * Scroll an element from a given value to another value
	 * 
	 * @param from
	 *            the starting value
	 * @param to
	 *            the final value
	 * @return the configuration object for easy chaining
	 */
	public AnimationData scroll(final Point from, final Point to) {
		scrollConfig.setProp("scroll", fromToObject(from.getJsArray(), to
				.getJsArray()));
		hasScrollConfig = true;
		return this;
	}

	/**
	 * Scroll an element from a given value to another value specified in given
	 * units.
	 * 
	 * @param from
	 *            the starting value
	 * @param to
	 *            the final value
	 * @param unit
	 *            any valid css unit ; "px", "em", "%" (default "px")
	 * @return the configuration object for easy chaining
	 */
	public AnimationData scroll(final Point from, final Point to,
			final String unit) {
		scrollConfig.setProp("scroll", fromToUnitObject(from.getJsArray(), to
				.getJsArray(), unit));
		hasScrollConfig = true;
		return this;
	}

	// ==============================================================
	// JSNI animation utilities
	// ==============================================================
	private static native final JavaScriptObject toObject(final int vTo) /*-{
		return {to: vTo};
	}-*/;

	private static native final JavaScriptObject toObject(final double vTo) /*-{
		return {to: vTo};
	}-*/;

	private static native final JavaScriptObject toObject(
			final JavaScriptObject vTo) /*-{
		return {to: vTo};
	}-*/;

	private static native final JavaScriptObject toUnitObject(final int vTo,
			final String vUnit) /*-{
		return {to: vTo, unit: vUnit};
	}-*/;

	private static native final JavaScriptObject toUnitObject(
			final JavaScriptObject vTo, final String vUnit) /*-{
		return {to: vTo, unit: vUnit};
	}-*/;

	private static native final JavaScriptObject fromToObject(final int vFrom,
			final int vTo) /*-{
		return {from: vFrom, to: vTo};
	}-*/;

	private static native final JavaScriptObject fromToObject(
			final double vFrom, final double vTo) /*-{
		return {from: vFrom, to: vTo};
	}-*/;

	private static native final JavaScriptObject fromToObject(
			final JavaScriptObject vFrom, final JavaScriptObject vTo) /*-{
		return {from: vFrom, to: vTo};
	}-*/;

	private static native final JavaScriptObject fromToUnitObject(
			final int vFrom, final int vTo, final String vUnit) /*-{
		return {from: vFrom, to: vTo, unit: vUnit};
	}-*/;

	private static native final JavaScriptObject fromToUnitObject(
			final JavaScriptObject vFrom, final JavaScriptObject vTo,
			final String vUnit) /*-{
		return {from: vFrom, to: vTo, unit: vUnit};
	}-*/;

	private static native void setMovePath(JavaScriptObject conf,
			JavaScriptObject val) /*-{
		conf.points.control = val;
	}-*/;

	// ==============================================================
	// All these accessors are for exclusive use by the
	// Animation class (They have package visibility)
	// ==============================================================

	/**
	 * Get the easing method object. For use in the package!
	 * 
	 * @return the easing method.
	 */
	final AnimationEffect getAnimationEffect() {
		return easing;
	}

	/**
	 * Get the easing method as JavaScript. For use in the package!
	 * 
	 * @return the easing method as JavaScript.
	 */
	final JavaScriptObject getJsAnimationEffect() {
		return (easing == null ? AnimationEffect.easeNone : easing)
				.getJsElement();
	}

	/**
	 * Get the duration. For use in the package!
	 * 
	 * @return duration
	 */
	final double getDuration() {
		return duration;
	}

	/**
	 * Do we have to build an attributeAnimation ?
	 * 
	 * @return true if an attributeAnimation is needed
	 */
	final boolean needAttributeAnimation() {
		return hasAttributeConfig;
	}

	/**
	 * Do we have to build an colorAnimation ?
	 * 
	 * @return true if an colorAnimation is needed
	 */
	final boolean needColorAnimation() {
		return hasColorConfig;
	}

	/**
	 * Do we have to build an MotionAnimation ?
	 * 
	 * @return true if an MotionAnimation is needed
	 */
	final boolean needMotionAnimation() {
		return hasMotionConfig;
	}

	/**
	 * Do we have to build an ScrollAnimation ?
	 * 
	 * @return true if an ScrollAnimation is needed
	 */
	final boolean needScrollAnimation() {
		return hasScrollConfig;
	}

	/**
	 * get the attributeConfig javascript
	 * 
	 * @return the attributeConfig javascript
	 */
	final JavaScriptObject getJsAttributeAnimation() {
		return attributeConfig.getJsElement();
	}

	/**
	 * get the colorConfig javascript
	 * 
	 * @return the colorConfig javascript
	 */
	final JavaScriptObject getJsColorAnimation() {
		return colorConfig.getJsElement();
	}

	/**
	 * get the MotionConfig javascript
	 * 
	 * @return the MotionConfig javascript
	 */
	final JavaScriptObject getJsMotionAnimation() {
		return motionConfig.getJsElement();
	}

	/**
	 * get the ScrollConfig javascript
	 * 
	 * @return the ScrollConfig javascript
	 */
	final JavaScriptObject getJsScrollAnimation() {
		return scrollConfig.getJsElement();
	}
}
